package com.atguigu.gulimall.order.service.impl;

import com.alibaba.fastjson.TypeReference;
import com.atguigu.common.constant.AuthConstant;
import com.atguigu.common.exception.BizCodeEnum;
import com.atguigu.common.to.HasStockVo;
import com.atguigu.common.to.MemberVo;
import com.atguigu.common.to.mq.OrderTo;
import com.atguigu.common.to.mq.SeckillOrderTo;
import com.atguigu.common.utils.R;
import com.atguigu.gulimall.order.constant.OrderConstant;
import com.atguigu.gulimall.order.constant.OrderStatusEnum;
import com.atguigu.gulimall.order.entity.OrderItemEntity;
import com.atguigu.gulimall.order.entity.PaymentInfoEntity;
import com.atguigu.gulimall.order.feign.OrderCartService;
import com.atguigu.gulimall.order.feign.OrderMemberService;
import com.atguigu.gulimall.order.feign.OrderProductService;
import com.atguigu.gulimall.order.feign.OrderWareService;
import com.atguigu.gulimall.order.interceptor.LoginUserInterceptor;
import com.atguigu.gulimall.order.service.OrderItemService;
import com.atguigu.gulimall.order.service.PaymentInfoService;
import com.atguigu.gulimall.order.to.CreateOrderTo;
import com.atguigu.gulimall.order.vo.*;
import com.baomidou.mybatisplus.core.toolkit.IdWorker;
import org.springframework.amqp.AmqpException;
import org.springframework.amqp.core.AmqpTemplate;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;

import java.math.BigDecimal;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.atguigu.common.utils.PageUtils;
import com.atguigu.common.utils.Query;

import com.atguigu.gulimall.order.dao.OrderDao;
import com.atguigu.gulimall.order.entity.OrderEntity;
import com.atguigu.gulimall.order.service.OrderService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;


@Service("orderService")
public class OrderServiceImpl extends ServiceImpl<OrderDao, OrderEntity> implements OrderService {

    @Autowired
    OrderMemberService orderMemberService;

    @Autowired
    OrderCartService orderCartService;

    @Autowired
    ThreadPoolExecutor executor;

    @Autowired
    OrderWareService orderWareService;

    @Autowired
    StringRedisTemplate redisTemplate;

    @Autowired
    OrderProductService orderProductService;

    @Autowired
    OrderItemService orderItemService;

    @Autowired
    AmqpTemplate amqpTemplate;

    @Autowired
    PaymentInfoService paymentInfoService;

    public ThreadLocal<OrderSumbitVo> orderSumbitVoThreadLocal = new ThreadLocal<>();

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>()
        );

        return new PageUtils(page);
    }

    @Override
    public PageUtils queryPageWithOrderItem(Map<String, Object> params) {

        MemberVo memberVo = LoginUserInterceptor.loginUser.get();

        IPage<OrderEntity> page = this.page(
                new Query<OrderEntity>().getPage(params),
                new QueryWrapper<OrderEntity>().eq("member_id",memberVo.getId())
                        .orderByDesc("id")
        );

        List<OrderEntity> records = page.getRecords();
        List<OrderEntity> collect = records.stream().map(item -> {
            List<OrderItemEntity> orderSn = orderItemService.list(new QueryWrapper<OrderItemEntity>().
                    eq("order_sn ", item.getOrderSn()));
            item.setItemEntities(orderSn);
            return item;
        }).collect(Collectors.toList());

        page.setRecords(collect);

        return new PageUtils(page);
    }

    /**
     * 根据秒杀服务传过来的消息创建订单，这里先简单处理
     * TODO 后续可完善
     * @param seckillOrderTo
     */
    @Override
    @Transactional
    public void handleSeckillOrder(SeckillOrderTo seckillOrderTo) {

        //创建订单
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(seckillOrderTo.getOrderSn());
        orderEntity.setMemberId(seckillOrderTo.getMemberId());
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());
        BigDecimal payAmount = seckillOrderTo.getSeckillPrice().multiply(new BigDecimal(seckillOrderTo.getNum() + ""));
        orderEntity.setPayAmount(payAmount);

        this.save(orderEntity);

        //保存订单项信息
        OrderItemEntity orderItem = new OrderItemEntity();
        orderItem.setOrderSn(seckillOrderTo.getOrderSn());
        orderItem.setSkuId(seckillOrderTo.getSkuId());
        orderItem.setRealAmount(payAmount);

        //TODO 获取当前sku的详细信息进行设置
        orderItem.setSkuQuantity(seckillOrderTo.getNum());

        orderItemService.save(orderItem);
    }

    @Override
    public String handlePayResult(PayAsyncVo payAsyncVo) throws ParseException {

        //1、保存交易流水
        PaymentInfoEntity paymentInfoEntity = new PaymentInfoEntity();
        paymentInfoEntity.setAlipayTradeNo(payAsyncVo.getTrade_no());
        paymentInfoEntity.setOrderSn(payAsyncVo.getOut_trade_no());
        paymentInfoEntity.setPaymentStatus(payAsyncVo.getTrade_status());
        System.out.println("notify_time:"+payAsyncVo.getNotify_time());
        SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
        Date notify_time = simpleDateFormat.parse(payAsyncVo.getNotify_time());
        paymentInfoEntity.setCallbackTime(notify_time);
        paymentInfoService.save(paymentInfoEntity);

        //2、修改订单的状态信息
        if ("TRADE_SUCCESS".equals(payAsyncVo.getTrade_status())
                || "TRADE_FINISHED".equals(payAsyncVo.getTrade_status())){
            String outTradeNo = payAsyncVo.getOut_trade_no();
            this.baseMapper.updateOrderStatus(outTradeNo,OrderStatusEnum.PAYED.getCode());
        }

        return "success";
    }

    @Override
    public OrderConfirmVo OrderConfirmInfo() throws ExecutionException, InterruptedException {

        ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = servletRequestAttributes.getRequest();
        MemberVo memberVo = (MemberVo) request.getSession().getAttribute(AuthConstant.LOGIN_USER);

        OrderConfirmVo orderConfirmVo = new OrderConfirmVo();


        //先远程查询收货地址
        CompletableFuture<Void> memberFuture = CompletableFuture.runAsync(() -> {
            //解决异步调用openfeign丢失请求头问题
            RequestContextHolder.setRequestAttributes(servletRequestAttributes);
            List<MemberAddressVo> memberAddressVos = orderMemberService.queryUserAddress(memberVo.getId());
            orderConfirmVo.setAddress(memberAddressVos);
        },executor);

        //查询所有选中的购物项，注意价格需要更新为最新的，需要查商品系统
        //openfeign调用的时候还能获取到session吗？
        //所谓的远程调用在被调用系统看来应该就是一次普通的http请求，
        //那么问题就转换为了：远程调用时会带上cookie等信息吗？
        //答案是不会，所以需要再openfeign发起请求前往template中加入cookie信息
        CompletableFuture<Void> cartFuture = CompletableFuture.runAsync(() -> {

            RequestContextHolder.setRequestAttributes(servletRequestAttributes);
            List<OrderItemVo> items = orderCartService.getCheckItem();
            orderConfirmVo.setItems(items);

        },executor).thenRunAsync(()->{
            List<Long> collect = orderConfirmVo.getItems().stream().map(item -> item.getSkuId()).collect(Collectors.toList());
            R r = orderWareService.hasStock(collect);
            List<HasStockVo> data = r.getData(new TypeReference<List<HasStockVo>>() {
            });
            if (data != null){
                Map<Long, Boolean> map = data.stream().collect(Collectors.toMap(
                        HasStockVo::getSkuId, HasStockVo::getHasStock
                ));
                orderConfirmVo.setStocks(map);
            }
        },executor);


        orderConfirmVo.setIntegration(memberVo.getIntegration());

        String token = UUID.randomUUID().toString();
        redisTemplate.opsForValue().set(
                OrderConstant.USER_ORDER_ANTI_REPLAY_TOKEN_PREFIX+memberVo.getId(),
                token,30, TimeUnit.MINUTES);

        orderConfirmVo.setOrderToken(token);


        CompletableFuture.allOf(memberFuture,cartFuture).get();

        return orderConfirmVo;
    }

    @Override
    public OrderEntity getByOrderSn(String orderSn) {
        OrderEntity orderEntity = this.baseMapper.
                selectOne(new QueryWrapper<OrderEntity>().eq("order_sn", orderSn));
        return orderEntity;
    }

    @Override
    @Transactional
    public void releaseOrder(OrderEntity order) {
        OrderEntity byId = getById(order.getId());
        if (byId != null && byId.getStatus() == OrderStatusEnum.CREATE_NEW.getCode()){
            doReleaseOrder(byId);
            OrderTo orderTo = new OrderTo();
            BeanUtils.copyProperties(byId,orderTo);
            amqpTemplate.convertAndSend("order-event-exchange",
                    "order.release.other",orderTo);
        }
    }

    private void doReleaseOrder(OrderEntity byId) {
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setId(byId.getId());
        orderEntity.setStatus(OrderStatusEnum.CANCLED.getCode());
        this.updateById(orderEntity);
    }


    @Transactional
    @Override
    public OrderSubmitResultVo submitOrder(OrderSumbitVo sumbitVo) {

        //下单：验令牌、创建订单、验价格、锁库存。。
        //下单成功则来到支付页
        //下单失败回到确认页重新确认订单信息

        orderSumbitVoThreadLocal.set(sumbitVo);

        OrderSubmitResultVo orderSubmitResultVo = new OrderSubmitResultVo();

        String orderToken = sumbitVo.getOrderToken();
        /**
         * 这里必须用Lua脚本保证token的对比和删除是一个原子操作
         */
        String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
        DefaultRedisScript<Long> redisScript = new DefaultRedisScript(script, Long.class);
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        MemberVo memberVo = (MemberVo) request.getSession().getAttribute(AuthConstant.LOGIN_USER);
        Long execute = redisTemplate.execute(redisScript,
                Arrays.asList(OrderConstant.USER_ORDER_ANTI_REPLAY_TOKEN_PREFIX + memberVo.getId()),
                orderToken
        );

        if (execute == 0L){
            //说明token验证不通过
            orderSubmitResultVo.setCode(BizCodeEnum.ORDER_TOKEN_INVALID_EXCEPTION.getCode());
            return orderSubmitResultVo;
        }

        //到这里说明token验证通过

        //创建订单
        CreateOrderTo createOrderTo = createOrder();

        //验价
        if (Math.abs(sumbitVo.getPayPrice().subtract(createOrderTo.getOrder().getPayAmount()).doubleValue()) < 0.01){
            //验价成功，保存订单
            saveOrder(createOrderTo);

            //锁定库存，只要有异常，应该全部回滚
            WareSkuLockVo wareSkuLockVo = new WareSkuLockVo();
            wareSkuLockVo.setOrderSn(createOrderTo.getOrder().getOrderSn());
            List<OrderItemLockVo> collect = createOrderTo.getOrderItems().stream().map(
                    item -> {
                        OrderItemLockVo orderItemVo = new OrderItemLockVo();
                        orderItemVo.setSkuId(item.getSkuId());
                        orderItemVo.setCount(item.getSkuQuantity());
                        orderItemVo.setTitle(item.getSkuName());
                        return orderItemVo;
                    }
            ).collect(Collectors.toList());
            wareSkuLockVo.setLocks(collect);
            R r = orderWareService.lockStock(wareSkuLockVo);
            if (r.getCode() == 0){
                orderSubmitResultVo.setCode(0);
                orderSubmitResultVo.setOrder(createOrderTo.getOrder());

                /**
                 * 自动关单思路梳理
                 * 下订单成功后，如果超时未支付等原因，应自动取消订单，因此发一个延时消息
                 * 后续接到消息后检查订单状态是否为待支付，如果是，则关闭订单
                 * 库存系统也有自动解锁库存的消息，并且延时比订单的消息更久，
                 * 一般情况下库存系统检查订单状态时如果未支付则为取消状态，因为订单服务已经改了状态
                 * 但万一因为消息延迟、服务器宕机等各种原因，订单解锁消息还没消费，即订单服务还在待支付状态
                 * 因为库存解锁是判断订单取消或为null才会解锁库存，因此什么也不会做，但库存解锁的消息已经被消费了
                 * 考虑到这一点，应该在订单关闭时主动发一个消息去解锁库存
                 * 实际上这样设计后库存解锁消息才是补充，订单消息触发的解锁库存消息才是主要解锁方式
                 *
                 * 这里还有一个问题，就是为什么要解锁两次库存呢？比如订单系统发送了解锁库存的消息，库存系统不发了行不行？
                 * 不行！
                 * 如果订单生成过程中出现异常，库存已经锁定成功，但订单生成失败，则根本不会发订单解锁消息，所以肯定要有库存系统发的消息
                 * 那如果库存系统判断订单仍为未支付或取消都自动解锁库存呢？我的想法是这个订单的问题是订单系统的事，库存系统的对订单的
                 * 判断逻辑必须简单，因为这是订单系统系统的事。只做自己该做的事，不能越位！
                 *
                 */

                //锁定成功
                //TODO 远程调用积分服务
                //String exchange, String routingKey, Object message
                //TODO 这里发消息可能会出现异常，比如网络闪断等，因此必须用try catch保证发出去。
                // 但网络断了可能一时间也没法恢复，重发也没用，所以最好是先记录发送的消息到一张日志表中
                // 定期扫描数据库将失败的消息再发送一次
                try {
                    amqpTemplate.convertAndSend("order-event-exchange","order.create.order",createOrderTo.getOrder());
                } catch (AmqpException e) {
                    //TODO 将没发送成功的消息进行重试发送
                }


//                System.out.println("by zero error");
//                int i = 1/0; //模拟失败

                return orderSubmitResultVo;
            }else {
                //锁定失败
                orderSubmitResultVo.setCode(BizCodeEnum.ORDER_NO_STOCK_EXCEPTION.getCode());
                /**
                 * 要保证库存锁定失败也直接回滚整个事务,最简单的办法就是直接抛异常
                 */
                throw new RuntimeException("库存锁定失败");
            }
        }else{
            //验价失败
            orderSubmitResultVo.setCode(BizCodeEnum.ORDER_PRICE_COMPARE_EXCEPTION.getCode());
            return orderSubmitResultVo;
        }
    }

    private void saveOrder(CreateOrderTo createOrderTo) {
        //1、保存orderEntity
        OrderEntity order = createOrderTo.getOrder();
        this.save(order);

        List<OrderItemEntity> orderItems = createOrderTo.getOrderItems();
        orderItemService.saveBatch(orderItems);

    }

    private CreateOrderTo createOrder() {

        //创建订单OrderEntity对象
        OrderEntity orderEntity = buildOrder();

        List<OrderItemEntity> orderItems =  buildOrderItems(orderEntity.getOrderSn());

        computeOrderAmount(orderEntity,orderItems);

        CreateOrderTo createOrderTo = new CreateOrderTo();
        createOrderTo.setOrder(orderEntity);
        createOrderTo.setOrderItems(orderItems);
        createOrderTo.setFare(orderEntity.getFreightAmount());
        createOrderTo.setPrice(orderEntity.getPayAmount());

        return createOrderTo;
    }

    private void computeOrderAmount(OrderEntity orderEntity,List<OrderItemEntity> orderItems) {

        BigDecimal total = BigDecimal.ZERO;

        BigDecimal couponAmount = BigDecimal.ZERO;
        BigDecimal integrationAmount = BigDecimal.ZERO;
        BigDecimal promotionAmount = BigDecimal.ZERO;

        BigDecimal giftIntegration = BigDecimal.ZERO;
        BigDecimal giftGrowth = BigDecimal.ZERO;


        for (OrderItemEntity orderItem : orderItems) {
            total = total.add(orderItem.getRealAmount());
            couponAmount = couponAmount.add(orderItem.getCouponAmount());
            integrationAmount = integrationAmount.add(orderItem.getIntegrationAmount());
            promotionAmount = promotionAmount.add(orderItem.getPromotionAmount());

            giftIntegration = giftIntegration.add(BigDecimal.valueOf(orderItem.getGiftIntegration()));
            giftGrowth = giftGrowth.add(BigDecimal.valueOf(orderItem.getGiftGrowth()));
        }

        orderEntity.setTotalAmount(total);
        //应付金额 = 订单总额 + 运费
        orderEntity.setPayAmount(total.add(orderEntity.getFreightAmount()));
        orderEntity.setCouponAmount(couponAmount);
        orderEntity.setPromotionAmount(promotionAmount);
        orderEntity.setIntegration(integrationAmount.intValue());

    }

    private List<OrderItemEntity> buildOrderItems(String orderSn) {

        List<OrderItemVo> orderItemVos = orderCartService.getCheckItem();
        if (orderItemVos != null && orderItemVos.size() > 0){
            List<OrderItemEntity> collect = orderItemVos.stream().map(
                    item->{
                        OrderItemEntity orderItemEntity = buildSingleOrderItem(item);
                        orderItemEntity.setOrderSn(orderSn);
                        return orderItemEntity;
                    }
            ).collect(Collectors.toList());
            return collect;
        }


        return null;
    }

    /**
     * 构造单个订单项
     * @param item
     * @return
     */
    private OrderItemEntity buildSingleOrderItem(OrderItemVo item) {
        OrderItemEntity orderItemEntity = new OrderItemEntity();
        //1.设置订单信息，主要是orderSn；orderSn已经能唯一确定订单，orderId就不需要设置了
        //2.商品的spu信息
        SpuInfoVo spuInfoVo = orderProductService.getSpuInfoBySkuId(item.getSkuId());
        orderItemEntity.setSpuBrand(spuInfoVo.getBrandName());
        orderItemEntity.setSpuId(spuInfoVo.getId());
        orderItemEntity.setSpuName(spuInfoVo.getSpuName());
        orderItemEntity.setSpuPic(spuInfoVo.getSpuImg());
        orderItemEntity.setCategoryId(spuInfoVo.getCatalogId());

        //3.商品的sku信息
        orderItemEntity.setSkuId(item.getSkuId());
        orderItemEntity.setSkuPic(item.getImage());
        String s = StringUtils.collectionToDelimitedString(item.getSkuAttrValues(), ";");
        orderItemEntity.setSkuAttrsVals(s);
        orderItemEntity.setSkuQuantity(item.getCount());
        orderItemEntity.setSkuName(item.getTitle());
        orderItemEntity.setSkuPrice(item.getPrice());

        //TODO 4.商品的优惠信息（先不做）
        orderItemEntity.setCouponAmount(BigDecimal.ZERO);
        orderItemEntity.setIntegrationAmount(BigDecimal.ZERO);
        orderItemEntity.setPromotionAmount(BigDecimal.ZERO);

        orderItemEntity.setRealAmount(item.getPrice().
                multiply(BigDecimal.valueOf(item.getCount()))
                .subtract(orderItemEntity.getCouponAmount())
                .subtract(orderItemEntity.getIntegrationAmount())
                .subtract(orderItemEntity.getPromotionAmount()));

        //5.成长积分信息
        orderItemEntity.setGiftIntegration(item.getPrice()
                .multiply(BigDecimal.valueOf(orderItemEntity.getSkuQuantity())).intValue());
        orderItemEntity.setGiftGrowth(item.getPrice()
                .multiply(BigDecimal.valueOf(orderItemEntity.getSkuQuantity())).intValue());

        return orderItemEntity;
    }

    private OrderEntity buildOrder() {
        String orderSn = IdWorker.getTimeId(); //订单号
        OrderEntity orderEntity = new OrderEntity();
        orderEntity.setOrderSn(orderSn);
        orderEntity.setStatus(OrderStatusEnum.CREATE_NEW.getCode());

        OrderSumbitVo orderSumbitVo = orderSumbitVoThreadLocal.get();
        R fare = orderWareService.fare(orderSumbitVo.getAddrId());
        FareVo fareVo = fare.getData(new TypeReference<FareVo>() {
        });
        orderEntity.setFreightAmount(fareVo.getFare());
        orderEntity.setCreateTime(new Date());
        orderEntity.setReceiverCity(fareVo.getAddress().getCity());
        orderEntity.setReceiverProvince(fareVo.getAddress().getProvince());
        orderEntity.setReceiverRegion(fareVo.getAddress().getRegion());
        orderEntity.setReceiverName(fareVo.getAddress().getName());
        orderEntity.setReceiverPhone(fareVo.getAddress().getPhone());

        //保存订单时把memberVo存上
        ServletRequestAttributes requestAttributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = requestAttributes.getRequest();
        HttpSession session = request.getSession();
        MemberVo memberVo = (MemberVo) session.getAttribute(AuthConstant.LOGIN_USER);
        orderEntity.setMemberId(memberVo.getId());

        orderEntity.setReceiverPostCode(fareVo.getAddress().getPostCode());

        orderEntity.setReceiverDetailAddress(fareVo.getAddress().getDetailAddress());

        orderEntity.setNote(orderSumbitVo.getNote());
        orderEntity.setDeleteStatus(0);

        return orderEntity;
    }
}